//----------------------------------------------------------------------------
dojo.require("dijit.dijit");
dojo.require("dojo.data.ItemFileWriteStore");
dojo.require("dojox.data.AndOrWriteStore");
dojo.require("dojox.grid.cells.dijit");
dojo.require("dojox.grid.EnhancedGrid");
dojo.require("dojox.grid.enhanced.plugins.DnD");
dojo.require("dojox.grid.enhanced.plugins.Menu");
dojo.require("dojox.grid.enhanced.plugins.NestedSorting");
dojo.require("dojox.grid.enhanced.plugins.IndirectSelection");
dojo.require("dojox.widget.PlaceholderMenuItem");

//Global object on which to hang functions. The SMD convention is to use 'smd' as the
//top level object onto which we tag functions etc.
if (window.smd === undefined) { var smd = {}; }
smd.grid = {};

/**
 * Build a grid widget with rows data and configuration.
 *
 * @param rows
 *          The rows data in json format
 * @param configuration
 *          configuration
 */
smd.grid.buildGrid = function(rows, configuration)
{
  // Create html tags for enhanced grid
  // Do initial nested sorting with JSON response, cause initial nested sorting
  // won't be supported util dojo 1.5.1

  var columnDefs = configuration.columnDefs || [];

  // Make datastore for enhanced grid
  var store = smd.grid.makeGridStore(configuration.identifier, configuration.label, rows, columnDefs);

  // Make layout for enhanced grid
  var layout = smd.grid.makeGridLayout(columnDefs, configuration.formatterParameters);

  // Get defualt parameters for enhanced grid
  var params = smd.grid.getGridDefaultParameters();

  // Set parameters
  dojo.mixin(params, configuration.params);

  if (configuration.plugins)
    params.plugins = configuration.plugins;

  params.store = store;
  params.structure = layout;

  var grid = new dojox.grid.EnhancedGrid(params, configuration.node);

  // Render the grid
  grid.startup();

  // header cell tooltip
  dojo.connect(grid, "onHeaderCellMouseOver", smd.grid.showHeaderCellTooltip);
  dojo.connect(grid, "onHeaderCellMouseOut", smd.grid.hideHeaderCellTooltip);

  // Set event handler
  dojo.connect(window, "resize", grid, "resize");

  // click cell to select row
  //grid.onCellClick = smd.grid.onCellClick;

  return grid;
};

/**
 * Get default parameters for enhanced grid.
 */
smd.grid.getGridDefaultParameters = function()
{
  var parameters =
  {
    id: "grid",
    noDataMessage: "There are no data retrieved",
    width: "100%",
    height: "",
    queryOptions: {
      ignoreCase: true
    },
    rowSelector: "20px",
    plugins:
    {
      nestedSorting: true,
      dnd: true,
      indirectSelection:
      {
        name: "",
        width: "25px",
        styles: "text-align: center;"
      }
    }
  };

  return parameters;
};

/**
 * Map column title and field name.
 *
 * @param columns
 *          The columns.
 */
smd.grid.makeFieldMap = function(columns)
{
  var fieldMap = {};
  dojo.forEach(columns, function(column)
  {
    fieldMap[column.title] = column.name;
  });

  return fieldMap;
};

/**
 * Create a data store for the grid
 *
 * @param identifier
 *          identifier
 * @param label
 *          label
 * @param rows
 *          The JSON data to populate the grid
 * @param columnDefs
 *          columnDefs
 */
smd.grid.makeGridStore = function(identifier, label, rows, columnDefs)
{
  var data = {
    identifier: identifier,
    label: label,
    items: rows
  };

  var store = new dojox.data.AndOrWriteStore(
  {
    data: data
  });

  // Set sort functions
  store.comparatorMap = {};
  dojo.forEach(columnDefs, function(columnDef)
  {
    if ("comparator" in columnDef)
      store.comparatorMap[columnDef.field] = columnDef.comparator;
  });

  return store;
};

/**
 * Create a layout specification for the grid.
 *
 * @param columnDefs
 *          columnDefs
 * @param formatterParameters
 *          formatterParameters
 */
smd.grid.makeGridLayout = function(columnDefs, formatterParameters)
{
  // View for locked columns
  var lockedColumns = [];

  // View for non-locked columns
  var nonLockedColumns = [];

  // Create layout
  dojo.forEach(columnDefs, function(columnDef)
  {
    var formatter = columnDefs.formatter || smd.grid.createColumnFormatter(columnDef, formatterParameters);

    var column =
    {
      name: columnDef.title,
      field: columnDef.field,
      hidden: columnDef.hidden || false,
      formatter: formatter
    };

    if("relWidth" in columnDef)
      column.relWidth = columnDef.relWidth;
    else
      column.width = columnDef.width; // || smd.grid.calcHeaderWidth(columnDef.title);
      
    if("noresize" in columnDef)
      column.noresize = columnDef.noresize;

    if("get" in columnDef)
      column.get = columnDef.get;

    if("value" in columnDef)
      column.value = columnDef.value;

    if("defaultValue" in columnDef)
      column.defaultValue = columnDef.defaultValue;

    if ("editable" in columnDef)
      column.editable = columnDef.editable;

    if ("type" in columnDef)
      column.type = columnDef.type;

    if ("options" in columnDef)
      column.options = columnDef.options;

    if ("widgetClass" in columnDef)
      column.widgetClass = columnDef.widgetClass;

    if ("widgetProps" in columnDef)
      column.widgetProps = columnDef.widgetProps;

    if(columnDef.locked === true)
      lockedColumns.push(column);
    else
      nonLockedColumns.push(column);
  });

  // locked view
  var lockedView =
  {
    noscroll: true,
    cells: lockedColumns
  };

  // non-locked view
  var nonLockedView =
  {
    cells: nonLockedColumns
  };

  var layout = [];

  if(lockedView.cells.length > 0)
    layout.push(lockedView);

  layout.push(nonLockedView);

  return layout;
};

smd.grid.charWidth = 9; //pixel

/**
 * Calculate the width of a header in grid.
 *
 * @param columnTitle
 *          The column title.
 */
smd.grid.calcHeaderWidth = function(columnTitle)
{
  var width = "";
  var charWidth = smd.grid.charWidth;

  if (columnTitle.length > 0)
    width = columnTitle.length;

  if (width < 5)
    width = 5;

  width += "em";
  return width;
};

/**
 * Create formatter for grid column.
 *
 * @param columnDefs
 *          columnDefs
 * @param formatterParameters
 *          formatterParameters
 */
smd.grid.createColumnFormatter = function(columnDef, formatterParameters)
{
  if (columnDef.formatter)
    return columnDef.formatter;

  var formatter = function(value, rowIndex)
  {
    var html = "";

    // Get row item
    var _self = this;
    var grid = this.grid;
    var store = grid.store;
    var item = grid.getItem(rowIndex);

    // set display value
    var displayValue = value;

    // format pattern
    var regExp = /\{(\d+|VALUE|MAPPED_VALUE)\}/g; // {VALUE} is the cell value of this column itself
    var _applyFormatterParameter = function(parameter, defaultValue)
    {
      var field = parameter.field;
      var value = field ? store.getValue(item, field) : defaultValue;

      var mapping = parameter.mapping;
      var mappedValue = mapping ? mapping.apply(_self, [value, rowIndex]) : value;

      return mappedValue;
    };

    var _replacer = function(str, paramIndex)
    {
      if (paramIndex === "VALUE")
        return value;

      if (paramIndex === "MAPPED_VALUE")
        return displayValue;

      var param = formatterParameters[paramIndex];
      if (!param)
        return str;

      var mappedValue = _applyFormatterParameter(param, value);
      return mappedValue;
    };

    var _formatPattern = function(pattern)
    {
      if (!pattern)
        return pattern;

      var formatted = pattern.replace(regExp, _replacer);
      return formatted;
    };

    // mapping value
    if (columnDef.mapping)
    {
      var mappedValue = columnDef.mapping.apply(this, arguments);
      displayValue = mappedValue;
    }

    // set cell class
    if (columnDef.cellClass)
    {
      var cellClassValue = null;
      if (typeof columnDef.cellClass === "string")
        cellClassValue = _formatPattern(columnDef.cellClass);
      else if (typeof columnDef.cellClass === "function")
        cellClassValue = columnDef.cellClass.apply(this, arguments);

      if (cellClassValue)
        this.customClasses.push(cellClassValue);
    }

    // set cell style
    if (columnDef.cellStyle)
    {
      var cellStyleValue = null;
      if (typeof columnDef.cellStyle === "string")
        cellStyleValue = _formatPattern(columnDef.cellStyle);
      else if (typeof columnDef.cellStyle === "function")
        cellStyleValue = columnDef.cellStyle.apply(this, arguments);

      if (cellStyleValue)
        this.customStyles.push(cellStyleValue);
    }

    // set content
    if (columnDef.content)
    {
      if (typeof columnDef.content === "string")
        displayValue = _formatPattern(columnDef.content);
      else if (typeof columnDef.content === "function")
        displayValue = columnDef.content.apply(this, arguments);
    }

    /*---- EXPERIMENT ----------------------------*/
    // highlight
    var keyword = smd.grid.getSearchKeyword();
    var searchField = smd.grid.getSearchField();
    if (searchField === "all" || searchField === this.field)
        displayValue = smd.grid.highlight(displayValue, keyword);
    /*---- EXPERIMENT ----------------------------*/

    html = displayValue;

    // check if show link
    var showLink = true;
    if ("showLink" in columnDef)
    {
      if (columnDef.showLink === false)
        showLink = false;
      else if (typeof columnDef.showLink === "function")
        showLink = columnDef.showLink.apply(this, arguments);
    }

    // set cell link
    if (showLink === true)
    {
      if (columnDef.link || columnDef.onclick)
      {
        var url = null;
        if (typeof columnDef.link === "string")
          url = _formatPattern(columnDef.link);

        var onclick = null;
        if (typeof columnDef.onclick === "string")
          onclick = _formatPattern(columnDef.onclick);

        var linkType = null;
        if (typeof columnDef.linkType === "string")
          linkType = columnDef.linkType;

        var linkTarget = null;
        if (typeof columnDef.linkTarget === "string")
          linkTarget = columnDef.linkTarget;

        var linkTitle = null;
        if (typeof columnDef.linkTitle === "string")
          linkTitle = columnDef.linkTitle;

        var link = smd.grid.buildLink(
        {
          text: displayValue,
          url: url,
          onclick: onclick,
          type: linkType,
          target: linkTarget,
          help: linkTitle
        });

        html = link;
      }
    }

    // show index (start from 1)
    if (columnDef.showIndex === true)
    {
      var index = rowIndex + 1;
      html = index + ". " + html;
    }

    return html;
  };

  return formatter;
};

/**
 * Return an HTML link built from the supplied style, text, url and onclick JavaScript.
 * @param {Object} args
 *      args.type - "popup" or null as default
 *      args.id
 *      args.text
 *      args.style
 *      args.class
 *      args.url
 *      args.onclick
 *      args.target
 *      args.help
 */
smd.grid.buildLink = function(args)
{
  //var link = smd.client.buildLink(args.type, args.text, args.url, args.onclick, args.help);
  var link = smd.grid._buildLink(args);
  return link;
};

/**
 * Return an HTML link built from the supplied style, text, url and onclick JavaScript.
 * @param {Object} args
 *      args.type - "popup" or null as default
 *      args.id
 *      args.text
 *      args.style
 *      args.class
 *      args.url
 *      args.onclick
 *      args.target
 *      args.help
 */
smd.grid._buildLink = function(args)
{
  // inner function to format string pattern
  var _format = function(pattern, value)
  {
    if (!pattern)
      return pattern;

    var regExp = /\{(\d+)\}/g; // such as {0}, {1}
    var formatted = pattern.replace(regExp, function(str, paramIndex)
    {
      return value;
    });

    return formatted;
  };

  // build attributes
  var attributes = [];

  // id
  if (args.id)
    attributes.push(_format('id="{0}"', args.id));

  // class
  if (args["class"])
    attributes.push(_format('class="{0}"', args["class"]));

  // style
  if (args.style)
    attributes.push(_format('style="{0}"', args.style));

  // url
  if (args.url)
    attributes.push(_format('href="{0}"', args.url));
  else
    attributes.push(_format('href="{0}"', "javascript://"));

  // onclick
  if (args.onclick)
    attributes.push(_format('onclick="{0}"', args.onclick));

  // target
  if (args.target)
    attributes.push(_format('target="{0}"', args.target));

  // help text
  if (args.help)
    attributes.push(_format('title="{0}"', args.help));

  // build link HTML
  var parts = [];
  parts.push("<a");
  parts.push(" ");
  parts.push(attributes.join(" "));
  parts.push(">");

  // If this is a popup link include the w3 popup image.
  if (args.type === "popup")
  {
    parts.push('<img src="../w3/images/icon-link-popup.gif" />');
    parts.push(" ");
  }

  // link text
  parts.push(args.text || "");

  parts.push("</a>");

  var link = parts.join("");
  return link;
};

/**
 * Add highlight effect to a string.
 * @param {Object} value
 * @param {Object} keyword
 */
smd.grid.highlight = function(value, keyword) {
    if (value === null || value === undefined) {
        return value;
    }

    if (!keyword) {
        return value;
    }

    // convert to string
    value =  "" + value;

    var highlightPattern = '<span class="highlight">{0}</span>';
    var pattern = keyword;
    var regExp = new RegExp(pattern, "ig");
    var highlighted = value.replace(regExp, function(str) {
        return demo.utils.format(highlightPattern, str);
    });

    return highlighted;
};

/**
 * 
 * @param {Object} grid
 * @param {Object} visibleOption true, false or null/undefined
 */
smd.grid.getGridColumns = function(grid, visibleOption)
{
  var columns = [];

  var condition = function(cell)
  {
    if (visibleOption === true)
      return !cell.hidden;

    if (visibleOption === false)
      return cell.hidden === true;

    return true;
  };

  dojo.forEach(grid.layout.cells, function(cell)
  {
    // skip indirection cell
    if (cell.declaredClass.search("RowSelector") > -1)
      return;

    if (condition(cell))
    {
      columns.push(
      {
        field: cell.field,
        name: cell.name,
        show: !cell.hidden
      });
    }
  });

  return columns;
};

/**
 * Select the row when clicks on the cell.
 *
 * @param {Object} e
 */
smd.grid.onCellClick = function(e)
{
  // Overwritten, see dojox.grid._Events.onCellClick()
  if (this.isDndSelectEnable)
    this.select.drugSelectRow(e.rowIndex);
  else
    this.inherited(arguments);
};


////////////////////////////////////////////////////////////
// Search
////////////////////////////////////////////////////////////

/**
 * Override
 */
smd.grid.getSearchKeyword = function()
{
  return null;
};

/**
 * Override
 */
smd.grid.getSearchField = function()
{
  return null;
};

smd.grid.search = function(grid, keyword, field)
{
  var query = null;
  if (keyword)
  {
    var queryPattern = smd.grid.getQueryPattern(field, grid);
    var complexQuery = demo.utils.format(queryPattern, keyword);
    query = { complexQuery: complexQuery };
    //console.debug(complexQuery);
  }

  // do filter
  grid.filter(query);
};

smd.grid.getQueryPattern = function(field, grid)
{
  var fields = [];

  field = field || "all";
  if (field === "all")
  {
    var visibleColumns = smd.grid.getGridColumns(grid, true);
    fields = dojo.map(visibleColumns, function(column)
    {
      return column.field;
    });
  }
  else
    fields = [field];

  var patterns = dojo.map(fields, function(field)
  {
    return field + ": '*{0}*'";
  });

  var queryPattern = patterns.join(" OR ");
  return queryPattern;
};

/**
 * Format metadata description items.
 *
 * @param node
 *          The DOM node to contain formatted metadata.
 * @param metadata
 *          The list of metadata description items.
 */
smd.grid.formatMetadata = function(node, metadata)
{
  var container = dojo.create("small", null, node);
  var table = dojo.place('<table align="center"></table>', container);
  var tbody = dojo.create("tbody", null, table);

  dojo.forEach(metadata, function(item)
  {
    if (item.name)
    {
      var tr = dojo.create("tr", null, tbody);
      var td = dojo.create("td", {
          align: "right",
          innerHTML: item.name
      }, tr);
      dojo.addClass(td, "dojoxLegendText");
      var td2 = dojo.create("td", {
          innerHTML: ":"
      }, tr);
      dojo.addClass(td2, "dojoxLegendText");
      var td3 = dojo.create("td", {
          innerHTML: item.value
      }, tr);
      dojo.addClass(td3, "dojoxLegendText");
    }
  });
};

/**
 * Show header tooltip.
 */
smd.grid.showHeaderCellTooltip = function(e)
{
  var msg = e.cell.name;
  if (msg != "")
  {
    dijit.showTooltip(msg, e.cellNode);
  }
};

/**
 * Hide header tooltip.
 */
smd.grid.hideHeaderCellTooltip = function(e)
{
  dijit.hideTooltip(e.cellNode);
  // FIXME: make sure that pesky tooltip doesn't reappear!
  // would be nice if there were a way to hide tooltip without regard to
  // aroundNode.
  dijit._masterTT._onDeck = null;
};

/**
 * Create a column selector header menu by using PlaceholderMenuItem.
 *
 * @param grid
 *          The grid widget.
 */
smd.grid.buildColumnSelectorHeaderMenu = function(grid)
{
  var menu = new dijit.Menu(
  {
    id: "columnSelectorMenu"
  });

  var placeholderMenuItem = new dojox.widget.PlaceholderMenuItem(
  {
    label: "GridColumns"
  });
  menu.addChild(placeholderMenuItem);

  grid.attr("headerMenu", menu);
  grid.setupHeaderMenu();

  return menu;
};

/**
 * Create the column selector menu.
 *
 * @param grid
 *          The grid widget.
 */
smd.grid.createColumnSelectorMenu = function(grid)
{
  var menu = new dijit.Menu(
  {
    id: "columnSelectorMenu"
  });

  dojo.forEach(grid.layout.cells, function(cell)
  {
    if (cell.name && cell.name.length > 0)
    {
      var item = new dijit.CheckedMenuItem(
      {
        label: cell.name,
        checked: !cell.hidden,
        _gridCell: cell,
        onChange: function(checked)
        {
          grid.layout.setColumnVisibility(this._gridCell.index, checked);
        },
        destroy: function()
        {
          var index = dojo.indexOf(this._gridCell.menuItems, this);
          this._gridCell.menuItems.splice(index, 1);
          delete this._gridCell;
          dijit.CheckedMenuItem.prototype.destroy.apply(this, arguments);
        }
      });
      menu.addChild(item);
    }
  });

  return menu;
};
